package top.v5it.japi.plus.spring.boot.autoconfigure;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.ListUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.lang.Nullable;
import top.v5it.japi.plus.core.ConstantPool;
import top.v5it.japi.plus.core.cache.JapiCache;
import top.v5it.japi.plus.core.spring.JapiAuditorAware;
import top.v5it.japi.plus.core.util.CertUtil;
import top.v5it.japi.plus.data.support.entity.*;
import top.v5it.japi.plus.data.support.repository.EntityDataRepository;
import top.v5it.japi.plus.data.support.repository.impl.JdbcEntityDataRepository;
import top.v5it.japi.plus.data.support.repository.impl.JpaEntityDataRepository;
import top.v5it.japi.plus.spring.boot.autoconfigure.properties.JapiProperties;
import top.v5it.japi.plus.support.AbstractJapiLoadRedisSet;
import top.v5it.japi.plus.support.JapiInitializedListener;
import top.v5it.japi.plus.support.idcenter.IdCenter;
import top.v5it.japi.plus.support.idcenter.impl.RedisGenerateIdCenter;
import top.v5it.japi.plus.support.idcenter.impl.SnowflakeIdCenter;
import top.v5it.japi.plus.support.limit.JapiLimitHandler;
import top.v5it.japi.plus.support.limit.JapiLimitWithCountHandler;
import top.v5it.japi.plus.support.limit.JapiLimitWithWindowHandler;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Map;

/**
 * 对框架所支持的组件进入注入
 *
 * @author zhanpu
 * @date 2022/6/14
 */
@Slf4j
public class SupportComponentConfiguration {

    private static final String SPRING_JAPI_LIMIT = "spring.japi.limit";
    private static final String SPRING_JAPI_INIT = "spring.japi.init";
    private static final String SPRING_JAPI_ID_CENTER = "spring.japi.idcenter";
    private static final String SPRING_JAPI_ID_CENTER_TYPE = "spring.japi.idcenter.id-type";

    /**
     * id中心配置
     */
    @Setter
    @ConfigurationProperties(SPRING_JAPI_ID_CENTER)
    @ConditionalOnProperty(name = SPRING_JAPI_ID_CENTER_TYPE, matchIfMissing = true)
    static class IdCenterConfiguration {

        /**
         * 终端ID
         */
        private long workerId;

        /**
         * 数据中心ID
         */
        private long datacenterId;

        /**
         * 雪花算法生成id
         *
         * @return {@link IdCenter}
         */
        @Bean
        @ConditionalOnProperty(name = SPRING_JAPI_ID_CENTER_TYPE, havingValue = "snow")
        @ConditionalOnMissingBean
        public IdCenter snowflakeIdCenter() {
            log.info(">>>>>>>>>>[Init SnowflakeIdCenter]<<<<<<<<<<");
            return new SnowflakeIdCenter(workerId, datacenterId);
        }

        /**
         * 基于redis生成id
         *
         * @param redisTemplate {@link RedisTemplate}
         * @return {@link IdCenter}
         */
        @Bean
        @ConditionalOnProperty(name = SPRING_JAPI_ID_CENTER_TYPE, havingValue = "redis")
        @ConditionalOnMissingBean
        public IdCenter redisGenerateIdCenter(RedisTemplate<String, Object> redisTemplate) {
            log.info(">>>>>>>>>>[Init RedisGenerateIdCenter]<<<<<<<<<<");
            return new RedisGenerateIdCenter(redisTemplate);
        }

    }

    /**
     * 限流配置
     */
    @ConditionalOnProperty(name = SPRING_JAPI_LIMIT, matchIfMissing = true)
    static class LimitConfiguration {

        /**
         * 为当前应用程序注入计数法限流器
         *
         * @param redisTemplate {@link RedisTemplate}
         * @return {@link JapiLimitHandler}
         */
        @Bean
        @ConditionalOnProperty(name = SPRING_JAPI_LIMIT, havingValue = "count")
        @ConditionalOnMissingBean
        JapiLimitHandler japiLimitWithCountHandler(RedisTemplate<String, Object> redisTemplate) {
            log.info(">>>>>>>>>>[Init JapiLimitWithCountHandler]<<<<<<<<<<");
            return new JapiLimitWithCountHandler(ConstantPool.getCachePrefix(), redisTemplate);
        }

        /**
         * 为当前应用程序注入滑动窗口法限流器
         *
         * @param redisTemplate {@link RedisTemplate}
         * @return {@link JapiLimitHandler}
         */
        @Bean
        @ConditionalOnProperty(name = SPRING_JAPI_LIMIT, havingValue = "sliding_win")
        @ConditionalOnMissingBean
        JapiLimitHandler japiLimitWithWindowHandler(RedisTemplate<String, Object> redisTemplate) {
            log.info(">>>>>>>>>>[Init JapiLimitWithWindowHandler]<<<<<<<<<<");
            return new JapiLimitWithWindowHandler(ConstantPool.getCachePrefix(), redisTemplate);
        }
    }

    /**
     * 按name属性填写，如若按value属性填写，当value值不存在报 nested exception is java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
     * <p>
     * 当org.springframework.data.jdbc.repository.support.SimpleJdbcRepository存在时初始化
     */
    @ConditionalOnClass(name = "org.springframework.data.jdbc.repository.support.SimpleJdbcRepository")
    @EnableJdbcAuditing
    static class JdbcAutoConfiguration {

        /**
         * 注入当前程序审计信息
         *
         * @return {@link JapiAuditorAware}
         */
        @Bean
        JapiAuditorAware japiAuditorAware() {
            log.info(">>>>>>>>>>[Init JapiAuditorAware]<<<<<<<<<<");
            return new JapiAuditorAware();
        }

        /**
         * 为当前应用程序注入以jdbc加载的实体数据
         *
         * @param jdbcTemplate {@link JdbcTemplate}
         * @return {@link EntityDataRepository}
         */
        @Bean
        EntityDataRepository entityDataService(JdbcTemplate jdbcTemplate) {
            log.info(">>>>>>>>>>[Init JdbcEntityDataServiceImpl]<<<<<<<<<<");
            return new JdbcEntityDataRepository(jdbcTemplate);
        }

    }

    /**
     * 按name属性填写，如若按value属性填写，当value值不存在报 nested exception is java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
     * <p>
     * 当org.springframework.data.jdbc.repository.support.SimpleJdbcRepository存在时初始化
     */
    @ConditionalOnClass(name = "org.springframework.data.jpa.repository.support.SimpleJpaRepository")
    @EnableJpaAuditing
    static class JpaAutoConfiguration {

        /**
         * 注入当前程序审计信息
         *
         * @return {@link JapiAuditorAware}
         */
        @Bean
        JapiAuditorAware japiAuditorAware() {
            log.info(">>>>>>>>>>[Init JapiAuditorAware]<<<<<<<<<<");
            return new JapiAuditorAware();
        }

        /**
         * 为当前应用程序注入以jpa加载的实体数据
         *
         * @param em {@link EntityManager}
         * @return {@link EntityDataRepository}
         */
        @Bean
        EntityDataRepository entityDataService(EntityManager em) {
            log.info(">>>>>>>>>>[Init JpaEntityDataRepository]<<<<<<<<<<");
            return new JpaEntityDataRepository(em);
        }

    }

    /**
     * 服务器证书加载
     */
//    static class CertLoadRedisSet extends AbstractJapiLoadRedisSet<Cert> {
//
//        private final EntityDataRepository entityDataRepository;
//
//        public CertLoadRedisSet(RedisTemplate<String, Object> redisTemplate, EntityDataRepository entityDataRepository) {
//            super(redisTemplate, ConstantPool.getCacheServerCertKey(), ConstantPool.getCacheShardLockServerCertKey(), "serialNo");
//            this.entityDataRepository = entityDataRepository;
//        }
//
//        @Override
//        protected List<Cert> dataAll() {
//            return entityDataRepository.certs();
//        }
//    }

    /**
     * 服务器证书加载
     */
    static class CertLoadRedisSet extends AbstractJapiLoadRedisSet<Cert> {

        private final JapiProperties japiProperties;

        public CertLoadRedisSet(RedisTemplate<String, Object> redisTemplate, JapiProperties japiProperties) {
            super(redisTemplate, ConstantPool.getCacheServerCertKey(), ConstantPool.getCacheShardLockServerCertKey(), "serialNo");
            this.japiProperties = japiProperties;
        }

        @Override
        protected List<Cert> dataAll() {
            final String privateKey = CertUtil.readPrivateKey(japiProperties.getSsl().getPrivateKeyFilepath());
            final Map<String, Object> objectMap = CertUtil.readX509CertificateForMap(japiProperties.getSsl().getCertFilepath());
            return ListUtil.toList(BeanUtil.mapToBean(objectMap, Cert.class, false, CopyOptions.create()).setPrivateKey(privateKey));
        }
    }

    /**
     * 接入渠道编号加载
     */
    static class ChannelLoadRedisSet extends AbstractJapiLoadRedisSet<Channel> {

        private final EntityDataRepository entityDataRepository;

        public ChannelLoadRedisSet(RedisTemplate<String, Object> redisTemplate, EntityDataRepository entityDataRepository) {
            super(redisTemplate, ConstantPool.getCacheChannelKey(), ConstantPool.getCacheShardLockChannelKey(), "channel");
            this.entityDataRepository = entityDataRepository;
        }

        @Override
        protected List<Channel> dataAll() {
            return entityDataRepository.channels();
        }
    }

    /**
     * 客户端证书加载
     */
    static class ClientCertLoadRedisSet extends AbstractJapiLoadRedisSet<ClientCert> {

        private final EntityDataRepository entityDataRepository;

        public ClientCertLoadRedisSet(RedisTemplate<String, Object> redisTemplate, EntityDataRepository entityDataRepository) {
            super(redisTemplate, ConstantPool.getCacheClientCertKey(), ConstantPool.getCacheShardLockClientCertKey(), "serialNo");
            this.entityDataRepository = entityDataRepository;
        }

        @Override
        protected List<ClientCert> dataAll() {
            return entityDataRepository.clientCerts();
        }
    }

    /**
     * 场所编号与接入渠道编号加载
     */
    static class SiteChannelLoadRedisSet extends AbstractJapiLoadRedisSet<SiteChannel> {

        private final EntityDataRepository entityDataRepository;

        public SiteChannelLoadRedisSet(RedisTemplate<String, Object> redisTemplate, EntityDataRepository entityDataRepository) {
            super(redisTemplate, ConstantPool.getCacheSiteChannelKey(), ConstantPool.getCacheShardLockSiteChannelKey(), "siteId");
            this.entityDataRepository = entityDataRepository;
        }

        @Override
        protected List<SiteChannel> dataAll() {
            return entityDataRepository.siteChannels();
        }
    }

    /**
     * 带密钥的接入渠道加载
     */
    static class KeyChannelLoadRedisSet extends AbstractJapiLoadRedisSet<KeyChannel> {

        private final EntityDataRepository entityDataRepository;

        public KeyChannelLoadRedisSet(RedisTemplate<String, Object> redisTemplate, EntityDataRepository entityDataRepository) {
            super(redisTemplate, ConstantPool.getCacheKeyChannelKey(), ConstantPool.getCacheShardLockKeyChannelKey(), "channel");
            this.entityDataRepository = entityDataRepository;
        }

        @Override
        protected Iterable<KeyChannel> dataAll() {
            return entityDataRepository.keyChannels();
        }
    }

    /**
     * 初始化加载配置
     */
    @ConditionalOnBean(EntityDataRepository.class)
    static class JapiLoadRedisSetConfiguration {

        private final RedisTemplate<String, Object> redisTemplate;
        private final EntityDataRepository entityDataRepository;

        public JapiLoadRedisSetConfiguration(RedisTemplate<String, Object> redisTemplate, EntityDataRepository entityDataRepository) {
            this.redisTemplate = redisTemplate;
            this.entityDataRepository = entityDataRepository;
        }

        /**
         * 注入服务器证书加载
         *
         * @return {@link CertLoadRedisSet}
         */
        @Bean
        CertLoadRedisSet certLoadRedisSet(JapiProperties japiProperties) {
            log.info(">>>>>>>>>>[Init CertLoadRedisSet]<<<<<<<<<<");
            return new CertLoadRedisSet(redisTemplate, japiProperties);
        }

        /**
         * 注入客户端证书加载
         *
         * @return {@link ClientCertLoadRedisSet}
         */
        @Bean
        ClientCertLoadRedisSet clientCertLoadRedisSet() {
            log.info(">>>>>>>>>>[Init ClientCertLoadRedisSet]<<<<<<<<<<");
            return new ClientCertLoadRedisSet(redisTemplate, entityDataRepository);
        }

        /**
         * 注入接入渠道加载
         *
         * @return {@link ChannelLoadRedisSet}
         */
        @Bean
        ChannelLoadRedisSet channelLoadRedisSet() {
            log.info(">>>>>>>>>>[Init ChannelLoadRedisSet]<<<<<<<<<<");
            return new ChannelLoadRedisSet(redisTemplate, entityDataRepository);
        }

        /**
         * 注入场所与接入渠道加载
         *
         * @return {@link SiteChannelLoadRedisSet}
         */
        @Bean
        SiteChannelLoadRedisSet siteChannelLoadRedisSet() {
            log.info(">>>>>>>>>>[Init SiteChannelLoadRedisSet]<<<<<<<<<<");
            return new SiteChannelLoadRedisSet(redisTemplate, entityDataRepository);
        }

        /**
         * 注入带密钥的接入渠道加载
         *
         * @return {@link KeyChannelLoadRedisSet}
         */
        @Bean
        KeyChannelLoadRedisSet keyChannelLoadRedisSet() {
            log.info(">>>>>>>>>>[Init KeyChannelLoadRedisSet]<<<<<<<<<<");
            return new KeyChannelLoadRedisSet(redisTemplate, entityDataRepository);
        }

        /**
         * 注入监听器，通过监听器加载所需要数据
         *
         * @return {@link JapiInitializedListener}
         */
        @Bean
        @ConfigurationProperties(SPRING_JAPI_INIT)
        JapiInitializedListener japiInitializedListener() {
            log.info(">>>>>>>>>>[Init JapiInitializedListener]<<<<<<<<<<");
            return new JapiInitializedListener();
        }
    }

    /**
     * {@link JapiCache}缓存注入
     *
     * @param ehCacheCacheManager 本地缓存
     * @param redisTemplate
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    JapiCache japiCache(@Nullable CacheManager ehCacheCacheManager, RedisTemplate<String, Object> redisTemplate) {
        log.info(">>>>>>>>>>[Init JapiCache]<<<<<<<<<<");
        return new JapiCache(ehCacheCacheManager, redisTemplate);
    }

}
